﻿using Axiom.Core;
using Axiom.Math;
using Engine.Input;

namespace Engine.Navigation 
{
    public class GlobeNavigationManager : NavigationManager
    {
        #region Private Attribute
       
        private float cursorX;
        private float cursorY;
        private Vector3 cameraOffset = Vector3.Zero;
        private static float cameraSpeedMax = 80f;
        private static float cameraSpeedMin = 0.01f;
        private static float cameraFarMax;
        private static float cameraNearMax;
        private static float cameraFarMin;
        private static float cameraNearMin;
        private static float cameraMinZoomFactor = 1.0f;
        private static float cameraMaxZoomFactor = 4.0f;
        private Vector3 cameraVector = Vector3.Zero;
        private float cameraScale;
        private Vector3 cameraVelocity = Vector3.Zero;
        private Vector3 cameraAcceleration = Vector3.Zero;
        private float cameraAccelerationConstant = 1f;
        private float cameraPitch = 0f;
        private float cameraYaw = 0f;
        private float cameraSpeed = 25f;
        
        #endregion

        #region Public Attributes

        private GlobeProjection projection;
        public GlobeProjection Projection
        {
            get { return projection; }
            set { projection = value; }
        }

        #endregion
        #region Default Nodes

        private SceneNode nodeCameraSphere;
        public SceneNode NodeCameraSphere
        {
            get { return nodeCameraSphere; }
            set { nodeCameraSphere = value; }
        }

        private SceneNode nodeCamera;
        public SceneNode NodeCamera
        {
            get { return nodeCamera; }
            set { nodeCamera = value; }
        }

        #endregion        

        public GlobeNavigationManager(SceneManager s, Camera c, InputReader i, System.Windows.Forms.Control u, float radius)
            : base(s, c, i, u)
        {
            Globe globe = new Globe(radius);
            Projection = new GlobeProjection(globe);
            camera.Position = Vector3.UnitX * radius * 2;
            camera.LookAt(Vector3.Zero);
            cameraFarMax = cameraFarMin = globe.OuterOrbitRadius * 8;
            cameraNearMax = globe.OuterOrbitRadius * 0.01f;
            cameraNearMin = globe.InnerOrbitRadius * 0.01f;
            cameraSpeedMax = globe.Radius * 4.0f;
            cameraSpeedMin = globe.Radius * 1.0f;
            camera.AutoAspectRatio = true;
            //camera.ProjectionType = Axiom.Graphics.Projection.Orthographic;

            #region Globe Navigation Node Initializations

            NodeCameraSphere = scene.RootSceneNode.CreateChildSceneNode("CameraSphere");
            NodeCamera = scene.CreateSceneNode("Camera");
            NodeCamera.Position = camera.Position;
            NodeCamera.Orientation = camera.Orientation;
            //NodeCamera.AttachObject(LightCamera);
            NodeCameraSphere.AddChild(NodeCamera);
            NodeCameraSphere.ScaleBy(Vector3.UnitScale * (cameraMaxZoomFactor + cameraMinZoomFactor) * 0.5f);

            #endregion
        }

        public override void Navigate(FrameEventArgs e)
        {
            // TODO : input.Capture bugs on GuiBase.onFrameStart why?
            input.Capture();
            
            #region Globe Navigation
            float timePass = /*e.TimeSinceLastFrame;*/0.05f;
            float scaleMove = 1 * e.TimeSinceLastFrame;

            // reset acceleration zero
            cameraAcceleration = Vector3.Zero;

            // set the scaling of camera motion
            cameraScale = 20 * timePass;

            cameraPitch = 0.0000f;
            cameraYaw = 0.0000f;
            cameraAcceleration.z = 0;

            if (input.IsMouseFocused)
            {
                cameraAcceleration.z = input.RelativeMouseZ * cameraAccelerationConstant * -0.0002f;

                if (input.IsMousePressed(MouseButtons.Left))
                {
                    cameraAcceleration.x = -input.RelativeMouseX * 0.5f;
                    cameraAcceleration.y = input.RelativeMouseY * 0.5f;
                }

                if (input.IsMousePressed(MouseButtons.Right))
                {
                    Vector3 cent = new Vector3((float)(userControl.Width / 2), (userControl.Height / 2), 0.0f);
                    Vector3 curr = new Vector3((float)input.AbsoluteMouseX, (float)input.AbsoluteMouseY, 0.0f) - cent;
                    Vector3 dist = new Vector3((float)input.RelativeMouseX, (float)input.RelativeMouseY, 0.0f);
                    Vector3 prev = curr - dist;
                    if (curr.Cross(prev).z > 0)
                    {
                        cameraYaw -= dist.Length * 0.1f;
                    }
                    else
                    {
                        cameraYaw += dist.Length * 0.1f;
                    }
                }

                //if (input.IsKeyPressed(KeyCodes.Up))
                //{
                //    cameraPitch += cameraScale;
                //}

                //if (input.IsKeyPressed(KeyCodes.Down))
                //{
                //    cameraPitch += -cameraScale;
                //}
                //if (input.IsKeyPressed(KeyCodes.Q))

                if (input.IsKeyPressed(KeyCodes.Shift))
                {
                    if (input.IsKeyPressed(KeyCodes.Left))
                    {
                        cameraYaw += cameraScale * 0.5f;
                    }

                    //if (input.IsKeyPressed(KeyCodes.E))
                    if (input.IsKeyPressed(KeyCodes.Right))
                    {
                        cameraYaw += -cameraScale * 0.5f;
                    }

                    //if (input.IsKeyPressed(KeyCodes.R))
                    if (input.IsKeyPressed(KeyCodes.Up))
                    {
                        cameraAcceleration.z = -0.002f * cameraAccelerationConstant;
                    }

                    //if (input.IsKeyPressed(KeyCodes.F))
                    if (input.IsKeyPressed(KeyCodes.Down))
                    {
                        cameraAcceleration.z = 0.002f * cameraAccelerationConstant;
                    }
                }
                else
                {
                    //if (input.IsKeyPressed(KeyCodes.A))
                    if (input.IsKeyPressed(KeyCodes.Left))
                    {
                        cameraAcceleration.x = -1 * cameraAccelerationConstant;
                    }

                    //if (input.IsKeyPressed(KeyCodes.D))
                    if (input.IsKeyPressed(KeyCodes.Right))
                    {
                        cameraAcceleration.x = 1 * cameraAccelerationConstant;
                    }

                    //if (input.IsKeyPressed(KeyCodes.W))
                    if (input.IsKeyPressed(KeyCodes.Up))
                    {
                        cameraAcceleration.y = 1 * cameraAccelerationConstant;
                    }

                    //if (input.IsKeyPressed(KeyCodes.S))
                    if (input.IsKeyPressed(KeyCodes.Down))
                    {
                        cameraAcceleration.y = -1 * cameraAccelerationConstant;
                    }
                }

            }

            cameraAcceleration.z *= 1000;

            #region OnStart

            cursorX = (float)input.AbsoluteMouseX / (float)userControl.Width;
            cursorY = (float)input.AbsoluteMouseY / (float)userControl.Height;

            if (cameraYaw != 0f)
            {
                Vector3 trace = NodeCamera.DerivedPosition;//camera.DerivedPosition;
                trace.Normalize();
                NodeCameraSphere.Rotate(trace, cameraYaw, TransformSpace.World);
                //System.Diagnostics.Debug.WriteLine("position is now x:" + Render.NodeCamera.DerivedPosition.x + " y:" + Render.NodeCamera.DerivedPosition.y + " z:" + Render.NodeCamera.DerivedPosition.z);
            }

            if ((cameraPitch > 0f && NodeCamera.Orientation.PitchInDegrees < 90f) || (cameraPitch < 0f && NodeCamera.Orientation.PitchInDegrees > 0f))
            {
                NodeCamera.Pitch(cameraPitch);
            }
            //Render.NodeCamera.Translate(new Vector3(0.0f, 0.0f, Render.RenderFactory.CameraOffset.z), TransformSpace.Local);
           
            #region Making Camera Corrections For Spherical Movement

            if (cameraOffset.z != 0f)
            {
                float curLength = NodeCamera.DerivedPosition.Length;
                float newLength = curLength + cameraOffset.z;
                float ZoomRate = newLength / curLength;
                NodeCameraSphere.ScaleBy(new Vector3(ZoomRate, ZoomRate, ZoomRate));
                if (NodeCameraSphere.Scale.x <= cameraMinZoomFactor)
                {
                    NodeCameraSphere.Scale = Vector3.UnitScale * cameraMinZoomFactor;
                }
                if (NodeCameraSphere.Scale.x > cameraMaxZoomFactor)
                {
                    NodeCameraSphere.Scale = Vector3.UnitScale * cameraMaxZoomFactor;
                }
            }
            #endregion

            //Vector3 left = camera.Up.Cross(camera.Direction);
            Vector3 left = camera.Up.Cross(camera.Direction);
            left /= left.Length;

            if (cameraOffset.y != 0f)
            {
                NodeCameraSphere.Rotate(left, cameraOffset.y, TransformSpace.World);
            }

            if (cameraOffset.x != 0f)
            {
                Vector3 up = left.Cross(camera.Position);
                up /= up.Length;
                NodeCameraSphere.Rotate(up, cameraOffset.x, TransformSpace.World);
            }
            //Render.NodeCameraSphere.Yaw(Render.RenderFactory.CameraRotation.x);
            //Render.NodeCameraSphere.Pitch(Render.RenderFactory.CameraRotation.y);
            //Render.NodeCameraSphere.Roll(Render.RenderFactory.CameraRotation.z);

            camera.Position = NodeCamera.DerivedPosition;
            camera.Orientation = NodeCamera.DerivedOrientation;

            #region Calculate Frustum By Exponential Interpolation

            CalculateFrustum(camera);

            CalculateSpeed(camera);

            #endregion

            #endregion

            cameraVelocity += (cameraAcceleration * scaleMove * cameraSpeed);

            // move the camera based on the accumulated movement vector

            cameraOffset = cameraVelocity * timePass;

            //Camera.MoveRelative(Vector3.UnitZ * offset.z);
            float currDistance = camera.Position.Length;

            if (cameraAcceleration == Vector3.Zero)
            {
                cameraVelocity *= (1 - (6 * timePass));
            }

            //camAccelConst = (ratio * (cameraAccMax  - cameraAccMin  )) + cameraAccMin;
            //camera.Direction = camera.Position * (-1);

            #endregion

        }

        #region Globe Navigation Functions
        private float GetCameraDistanceRatio(Camera camera)
        {
            float ratio = ((camera.Position.Length - Projection.Globe.InnerOrbitRadius) / (Projection.Globe.OuterOrbitRadius - Projection.Globe.InnerOrbitRadius));
            if (ratio < 0.0f)
            {
                ratio = 0.0f;
            }
            else if (ratio > 1.0f)
            {
                ratio = 1.0f;
            }
            return ratio;
        }
        private void CalculateSpeed(Camera camera)
        {
            float ratio = GetCameraDistanceRatio(camera);
            cameraSpeed = (ratio * (cameraSpeedMax - cameraSpeedMin)) + cameraSpeedMin;
        }
        private void CalculateFrustum(Camera camera)
        {
            float ratio = GetCameraDistanceRatio(camera);

            float far = (ratio * (cameraFarMax - cameraFarMin)) + cameraFarMin;
            float near = (ratio * (cameraNearMax - cameraNearMin)) + cameraNearMin;

            if (near <= cameraNearMin)
            {
                camera.Near = cameraNearMin;
            }
            else
            {
                camera.Near = near;
            }

            camera.Far = far;
        }
        private Vector3 GetWorldProjectedCursorPosition()
        {
            return GetWorldProjectedCursor(cursorX, cursorY);
        }
        private Vector3 GetWorldProjectedCursor(float x, float y)
        {
            if (x < 0.0f || y < 0.0f || x > 1.0f || y > 1.0f)
            {
                x = 0.5f;
                y = 0.5f;
            }
            Sphere world = new Sphere(Vector3.Zero, Projection.Globe.InnerOrbitRadius);
            Vector3 cameraPosition = camera.Position;
            Ray cameraRay = camera.GetCameraToViewportRay(x, y);
            Vector3 rayDirection = (cameraRay.Direction / cameraRay.Direction.Length);
            float rayDistance = cameraRay.Intersects(world).Distance;
            Vector3 wpc = (rayDirection * rayDistance) + cameraPosition;
            return wpc;
        }
        private Vector3 GetWorldProjectedCameraDirection()
        {
            return GetWorldProjectedCursor(0.5f, 0.5f);
        }
        private Vector3 GetWorldProjection(Vector3 point)
        {
            Vector3 traceOnEarth = point;
            traceOnEarth /= traceOnEarth.Length;
            traceOnEarth *= Projection.Globe.InnerOrbitRadius;
            return traceOnEarth;
        }
        #endregion
       
    }//end GlobeNavigation

}//end namespace Navigation